import java.util.LinkedList;
import java.util.ListIterator;
import java.util.Collections;
/**
 * Klasa opisujaca populacje osobnikow
 * @author pawel
 *
 */
public class populacja {
	private przedzial range;
	private int cardinality;
	/*
	 * Srednia i odchylenie dla
	 * funkcji ktorej ekstremum szukamy
	 */
	private double average;
	private double deviation;
	/*
	 * Srednia i odchylenie dla funkcji przystosowania
	 */
	private double accAverage;
	private double accDeviation;
	private osobnik best;
	private LinkedList<osobnik> popul;
/**
 * Tworzy nowa populacje z tablicy liczb zmiennoprzecinkowych i przedzialu
 * @param initialPopulation populacja poczatkowa
 * @param range przedzial
 */
	public populacja(double[] initialPopulation,przedzial range){
		this.range = range;
		this.popul = new LinkedList<osobnik>();
	this.cardinality = initialPopulation.length;
	for(int i=0; i < this.cardinality;i++){
		this.popul.push(new osobnik(range,initialPopulation[i]));
	}
	this.average = 0.0;
	this.deviation = 0.0;
	this.accAverage = 0.0;
	this.accDeviation = 0.0;
	}
/**
 * Tworzy losowa populacje o zadanej licznosci
 * @param populationSize licznosc
 * @param range przedzial
 */
	public populacja(int populationSize,przedzial range){
		this.range = range;
		this.popul = new LinkedList<osobnik>();
		this.average = 0.0;
		this.deviation = 0.0;
		this.accAverage = 0.0;
		this.accDeviation = 0.0;
		this.cardinality = populationSize;
		double rand;
		for(int i =0;i<populationSize;i++){
			rand  = Math.random()*(range.getTo() - range.getFrom()) + range.getFrom();
			this.popul.push(new osobnik(range,rand));
		}
	}
/**
 * Mutuje Osobniki populacji z zdanym prawdopodobienstwem	
 * @param probability prawdopodobienstwo
 */
	public void mutateWithProbability(double probability){
		ListIterator<osobnik> iter = this.popul.listIterator();
		
		while(iter.hasNext()){
			
				iter.next().mutate(probability);
				
		}
	}
	/**
	 * Oblicza wartosc funkcji przystosowania dla wszystkich osobnikow populacji
	 */
	public void evaluate(){
		this.average = 0.0;
		this.deviation = 0.0;
		this.accAverage = 0.0;
		this.accDeviation = 0.0;
		
		if( ! this.popul.isEmpty()){
			ListIterator<osobnik> iter = this.popul.listIterator();
			this.best = this.popul.getFirst();
			double min = Double.MAX_VALUE;
			
			double tmp;
			osobnik tmpOsobnik;
			while(iter.hasNext()){
				tmpOsobnik =(iter.next());
				tmp = (-1.0)*tmpOsobnik.getFunctionValue();
				tmpOsobnik.setAccommodationValue(tmp);
				this.average+=-tmp;
				this.accAverage+=tmp;
				
				if(tmp< min){
					min = tmp;
				}

				if(tmpOsobnik.getFunctionValue() < this.best.getFunctionValue()){
					this.best = new osobnik(tmpOsobnik);
				}
			}
			double add;
			add = Math.abs(1.001*min);
			this.accAverage+= this.popul.size()* add;
			double accSum = this.accAverage;
			this.accAverage/=this.popul.size();
			this.average/=this.popul.size();
			
			iter = this.popul.listIterator();
			double tmp2;
			while(iter.hasNext()){
				tmpOsobnik = iter.next();
				tmp = tmpOsobnik.getAccommodationValue() + add ;
				tmpOsobnik.setAccommodationValue(tmp);
				this.accDeviation+=(tmp - this.accAverage)*(tmp - this.accAverage);
				tmp = tmpOsobnik.getFunctionValue();
				this.deviation+=(tmp - this.average)*(tmp - this.average);
				
			}
			this.deviation/=this.popul.size() -1;
			this.deviation = Math.sqrt(this.deviation);
			this.accDeviation/=this.popul.size() -1;
			this.accDeviation = Math.sqrt(this.accDeviation);
			//accSum/=this.accDeviation;
			//this.accAverage/=this.accDeviation;
			
			double devScale = 3.0;
			double max = Double.MIN_VALUE;
			double tmpAccAverage=0.0;
			iter = this.popul.listIterator();
			while(iter.hasNext()){
				tmpOsobnik = iter.next();
				tmp = (tmpOsobnik.getAccommodationValue() - this.accAverage + devScale*this.accDeviation);
				//tmp = (tmp>0)? tmp:0;
				tmpAccAverage+=tmp;
				tmpOsobnik.setAccommodationValue(tmp);
				
				if(tmp>max){
					max = tmp;
				}
				
			}
			accSum = tmpAccAverage;
			this.accAverage = tmpAccAverage/this.popul.size();
			double alpha = 1.0;
			double beta = 0.0;
			double maxBest = 1.6;
			tmp = this.accAverage - max;
			if(Math.abs(tmp) > 20*Double.MIN_NORMAL){
				alpha = (this.accAverage*(1-maxBest))/tmp;
				beta = (this.accAverage*(maxBest* this.accAverage - max))/tmp;
			}
			
			
			
			
			
			iter = this.popul.listIterator();
			int N = this.cardinality;
			while(iter.hasNext()){
				tmpOsobnik = iter.next();
				//tmp = tmpOsobnik.getAccommodationValue()/this.accDeviation;
				tmp = alpha*tmpOsobnik.getAccommodationValue()  - beta;
				tmp = (tmp>0)? tmp:0;
				
				tmpOsobnik.setAccommodationValue(tmp);
				tmpOsobnik.setExpectedOffspringNumber(((tmp*N)/accSum));
			}
			//this.accDeviation/=this.accDeviation;
			
		}
	}
	/**
	 * Rekombinuje populacje czyli tworzy osobniki potomne z losowych par osobnikow rodzicielskich
	 * 
	 */
	public void recombination(){
		LinkedList<osobnik> offspringList = new LinkedList<osobnik>();
		int N,M;
		int x,y;
		N= this.popul.size();
		M = this.cardinality;
		osobnik tmpOs1, tmpOs2;
		osobnik[] offspringTab;
		int secLimit=1000;
		int secCounter;
		for(int i=0;i<N;i++){
			x = (int)(Math.random()*(M-1));
			secCounter=0;
			do{
				y = (int)(Math.random()*(M-1));
				secCounter++;
			}while( this.popul.get(x).equals(this.popul.get(y)) && secCounter<secLimit);
			//if(secCounter>= secLimit)continue;
			tmpOs1 = this.popul.get(x);
			tmpOs2 = this.popul.get(y);
			offspringTab = tmpOs1.crossover(tmpOs2);
			int randOffspringCount = (int)Math.round(Math.random()*3) +1;
			int randIndex;
			//System.out.println("OFF: " + randOffspringCount);
			for(int j =0;j<randOffspringCount;j++){
				randIndex = (int)(Math.random()*5 +1);
				//System.out.println("RandIndex: " + randIndex);
				offspringList.push(new osobnik(offspringTab[randIndex]));
			}
			
		}
		
//this.popul.addAll(offspringList);
	this.popul = offspringList;
		
	}
	/**
	 * Selekcja osobnikow do nastepnego pokolenia
	 * Realizuje podejscie selekcji deterministycznej
	 */
	public void selection(){
		Collections.sort(this.popul, osobnik.OffspringRandSort);
		Collections.sort(this.popul, osobnik.OffspringIntSort);
		LinkedList<osobnik> tmpOsList = new LinkedList<osobnik>();
		ListIterator<osobnik> iter = this.popul.listIterator();
		osobnik tmpOs;
		int N=0;
		boolean br =false;
		int tmp;
		while(iter.hasNext()){
			tmpOs = iter.next();
			tmp = tmpOs.getExpectedOffspringNumberInt();
			if(tmp == (this.cardinality -1))tmp--;
			for(int i=0;i<tmp;i++){
				if(N < this.cardinality){
					tmpOsList.push(new osobnik(tmpOs));
					N++;
				}
				else{
					br = true;
					break;
				}
			}
			if(br) break;
		}
		if(N< this.cardinality){
			Collections.sort(this.popul, osobnik.OffspringRandSort);
			Collections.sort(this.popul,osobnik.OffspringFractionSort);
			iter = this.popul.listIterator();
			while(iter.hasNext() && N < this.cardinality ){
				tmpOsList.push(new osobnik(iter.next()));
				N++;
			}
		}
		this.popul = tmpOsList;
	}

	public int getCardinality() {
		return this.cardinality;
	}

	public double getAverage() {
		return this.average;
	}

	public double getDeviation() {
		return this.deviation;
	}

	public osobnik getBest() {
		return this.best;
	}

	public LinkedList<osobnik> getPopul() {
		return this.popul;
	}

	@Override
	public String toString() {
		return "populacja [accAverage=" + accAverage + ", accDeviation="
				+ accDeviation + ", average=" + average
				+ ", deviation=" + deviation +  ", best=" + best + ", popul=" + popul + "]";
	}
	
	
}
